package application;
	
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Slider;
import javafx.scene.control.TextField;
import javafx.scene.control.TitledPane;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.stage.WindowEvent;
import CTCOffice.CTCOffice;
import CTCOffice.Router;
import CTCOffice.SelfEncrypter;
import MBO.MBO;
import TrackController.TrackController;
import TrackModel.Block;
import TrackModel.ControllerBlock;
import TrackModel.Line;
import TrackModel.Line.Train;
import TrackModel.StationBlock;
import TrackModel.TrackModel;
import TrainController.TrainController;
import TrainModel.TrainModel;
import Utilities.Database.DBEntry;
import Utilities.logFileIO;

public class Main extends Application {
	public static logFileIO log;

	public static Parent root, child;
	public static Scene scene, cscene;
	public static Stage stage, cstage;
	public static Canvas canvas;
	public static ContextMenu contextMenu;

	public static GraphicsEngine graphicsEngine;

	public static final long lTick = 1000;
	public static long sysTime = System.currentTimeMillis();
	public static Task<Void> task, graphicsThread;

	public static double WINDOW_WIDTH, WINDOW_HEIGHT;

	public static MBO mbo;
	public static TrackModel trackModel;
	public static ArrayList<TrainModel> trainModelList;
	public static ArrayList<TrainController> trainControlList;	//Needed since they belong on the trains???
	public static ArrayList<TrackController> trackControlList;
	
	public static HashMap<Integer,Label[]> trainPanes;
	public static ArrayList<MapElement> mapElements;
	public static ArrayList<MapElement> trainElements;
	public static ArrayList<MapElement> trackElements;
	public static ArrayList<MapElement> trackControllerElements;
	public static ArrayList<MapElement> mapHighlightElements;
	public static MapElement selected;

	public static boolean create = false;
	public static boolean multiSelect = false;
	public static SelfEncrypter cuLogin;
	
	public static int iSimulationMultiplier = 1;
	
	public static CTCOffice office;
	
	@Override
    public void start(Stage st) throws Exception {
    	//Init log file
    	log = new logFileIO("system", "txt");

		//Load and create scene from Login UI.fxml
		root = FXMLLoader.load(getClass().getResource("Login UI.fxml"));
		scene = new Scene(root, 410, 210);
		//Passes the generated stage to the global static, continues to setup window content and properties
		stage = st;
		stage.setTitle("OCSD Login");
        stage.initStyle(StageStyle.UTILITY);    
        stage.setScene(scene);
        stage.show(); 
    }
	
	public static void main(String[] args) {
		launch(args);
	}
	
	public static void configure() {
		try {
			Main.log.write("Application: User authenticated. Initializing application...");
			
	    	office = new CTCOffice();
	    	//mbo = new MBO();
	    	trainModelList = new ArrayList<TrainModel>();
			trainControlList = new ArrayList<TrainController>();
			trackControlList = new ArrayList<TrackController>();
	  		trackModel = new TrackModel();

	  		trainPanes = new HashMap<Integer,Label[]>();
	  		mapElements = new ArrayList<MapElement>();
			mapHighlightElements = new ArrayList<MapElement>();
			trainElements = new ArrayList<MapElement>();
			trackElements = new ArrayList<MapElement>();
			trackControllerElements = new ArrayList<MapElement>();
			
			//Sets up the tick()
			timedEvent();
			Main.log.write("Application: System tick started.");
	
	  		//Load the UI
			loadMainUI();
			graphicsRefresh();
			bindKeys();
			toggleMBO(); // Turns off the MBO
			Main.log.write("Application: Completed Initialization.");
		} catch(Exception e) {
			System.out.println(e.getStackTrace());
		}
	}

	public static void bindKeys() {
		//Adds key-down events
		root.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
	        @Override
	        public void handle(KeyEvent keyEvent) {
	            if(keyEvent.getCode() == KeyCode.CONTROL)
	            	multiSelect = true;	            	
	        }
	    });
		
		//Adds key-up events
		root.addEventFilter(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {
	        @Override
	        public void handle(KeyEvent keyEvent) {
	        	if(keyEvent.getCode() == KeyCode.CONTROL)
	            	multiSelect = false;	       
	        }
	    });
	}
	
	public void restartSystem() {
		Main.log.write("Application: User specified a reboot of the system.");
		try {
			final String javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
			final File currentJar = new File(Main.class.getProtectionDomain().getCodeSource().getLocation().toURI());
	
			//Cancels if the file found was not a jar
			if(!currentJar.getName().endsWith(".jar"))
				return;
	
			//java -jar ocsd.jar
			final ArrayList<String> command = new ArrayList<String>();
			command.add(javaBin);
			command.add("-jar");
			command.add(currentJar.getPath());
	
			final ProcessBuilder builder = new ProcessBuilder(command);
			builder.start();
			exitSystem();
		} catch(Exception ex) { }
	}
	
	/**
	 * Terminates the system with a success code.
	 */
	public static void exitSystem() {
		Main.log.write("Application: User exiting program, farewell cruel world.");
		Main.log.close();
		
		task.cancel();
		graphicsThread.cancel();

		System.exit(0);
	}
	
	/**
	 * Returns a boolean representing whether the user was successfully authenticated by the system.
	 * No parameters are taken since they are extracted directly from the TextFields on the UI.
	 * <p>
	 * This method will return false in the event of a failed authentication and will give visual
	 * indication to the user via text. Additional constructs are provided for creating a new user
	 * if the UI element is enabled to generate new encrypted data, which can be manually inserted
	 * into the JAR.
	 * 
	 * @return a boolean representing the state of authentication
	 * @see SelfEncrypter
	 */
	public static boolean authenticateUser() {
		//Gets the ID of the pUID and PID
		TextField tfUID = (TextField)root.lookup("#loginUID");
		TextField tfPID = (TextField)root.lookup("#loginPID");
		Label lError = (Label)root.lookup("#loginError");
		
		//Attempts to validate the user otherwise displays an error message
		SelfEncrypter seLogin = new SelfEncrypter(tfUID.getCharacters().toString(),tfPID.getCharacters().toString(), "/Users");
		
		//Checks to see if the user wants to create a new account
		CheckBox cbCreate = (CheckBox)root.lookup("#createAccount");
		if(cbCreate == null || !cbCreate.isSelected()) {
	        if(!seLogin.validate()) {
	        	lError.setTextFill(Color.web("#FF0000"));
	        	lError.setText("Username/Password Provided Failed Authentication");
	        	Main.log.write("Application: User failed authentication with credentials uname=" + tfUID.getCharacters().toString() + " pword=" + tfPID.getCharacters().toString());
	        	//exitSystem();
	        }
	        else {
	        	configure();
	        }
		} else if(create) {
			if(seLogin.validate()) {
				cuLogin.createUser();
				tfUID.clear();
				tfPID.clear();
				cbCreate.setSelected(false);
				lError.setTextFill(Color.web("#009900"));
				lError.setText("The Account Has Been Validated And Created");
			} else {
				create = false;
				tfUID.clear();
				tfPID.clear();
				cbCreate.setSelected(false);
				lError.setText("We Were Unable To Validate Your Account");
			}
		} else {
			//Sets the create variable, pends validation, stores the users credentials
			create = true;
			cuLogin = new SelfEncrypter(tfUID.getCharacters().toString(),tfPID.getCharacters().toString(), "/Users");
			tfUID.clear();
			tfPID.clear();
			tfUID.requestFocus();
        	lError.setText("Please Validate With An Existing Users Credentials");
		}
        
		return true;
	}
	
	/**
	 * Resets the stage of the main window to the main ui once a user has been successfully authenticated by the system.
	 * The window is set to a specific proportions in order to preserve drawings on the map. The canvas and graphics context
	 * is also instantiated here, which will be used to display the various GUI elements when the track model is rendered.
	 * 
	 * @return Parent node representing the container of the stage
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked")
	private static Parent loadMainUI() throws Exception {
		//Stores the screens bounds 
		WINDOW_WIDTH = Screen.getPrimary().getVisualBounds().getWidth();
		WINDOW_HEIGHT = Screen.getPrimary().getVisualBounds().getHeight();

		//Loads the fxml for the Main UI
        root = (Parent) FXMLLoader.load(Main.class.getResource("Main UI.fxml"));
        scene = stage.getScene();
        //Sets the default size of the screen
        scene = new Scene(root, WINDOW_WIDTH, WINDOW_HEIGHT);
        //Locks the screen at this resolution or full
        stage.setMinHeight(WINDOW_HEIGHT);
        stage.setMinWidth(WINDOW_WIDTH);
        stage.setX(0.0D);
        stage.setY(0.0D);
        stage.setTitle("OCSD Train Controller");
        stage.setScene(scene);
        stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
            @Override
            public void handle(WindowEvent event) {
                exitSystem();
            }
        });
        
        //Initialize the canvas setting
        initGraphics();
        calibrateScaling();
        drawTrack();
        graphicsEngine.render(mapHighlightElements, trackElements, mapElements, trainElements);

        Main.log.write("Application: World created and drawn.");
        return root;
    }
	
	private static void initGraphics() {		
		//Gets the dimensions of the content container
		ScrollPane apContentContainer = (ScrollPane)root.lookup("#ContentContainer");
		
		double cWidth = apContentContainer.getWidth() - 18;
		double cHeight = apContentContainer.getHeight() - 37;

		graphicsEngine = new GraphicsEngine(cHeight, cWidth);
		
        graphicsEngine.attachHandler(new EventHandler<MouseEvent>() {
        	@Override
        	public void handle(MouseEvent evt) {
        		if(evt.getButton().equals(MouseButton.PRIMARY) || evt.getButton().equals(MouseButton.SECONDARY)) {
        			//Logs the click location
					Main.log.write("Application: User click occured at <" + evt.getX() + "," + evt.getY() + ">.");

        			hideContextMenu();
        			selected = null;

        			//Checks if the user is performing a multi-select
        			if(!multiSelect)
        				mapHighlightElements.clear();

        			for(MapElement m : trainElements) {
	        			if(detectCollision(evt, m))
	        				return;
        			}
        			for(MapElement m : mapElements) {
	        			if(detectCollision(evt, m))
	        				return;
        			}
        			for(MapElement m : trackElements) {
	        			if(detectCollision(evt, m))
	        				return;
        			}
        			//System.out.println("Clicked: " + evt.getX() + "," + evt.getY());
        		}
        	}
        });
        
        AnchorPane apContent = (AnchorPane)root.lookup("#Content");
        apContent.getChildren().add(graphicsEngine.canvas);
	}

	private static boolean detectCollision(MouseEvent evt, MapElement m) {
		if(m.collision(evt.getX(), evt.getY())) {
			//Display the context menu only on right click
			if(evt.getButton().equals(MouseButton.SECONDARY))
				displayContextMenu(m, evt.getX(), evt.getY() + m.getOffset());
			
			mapHighlightElements.add(new MapElement(m));
			mapHighlightElements.get(mapHighlightElements.size() - 1).highlight();
			selected = m;

			//Checks to see if the element is a train, highlight the route
			if(m.getType() == MapElement.TRAIN) {
				//expandMenuContainer();
				//highlightRoute();
			}
			Main.log.write("Application: You hit a " + MapElement.getName(m.getType()));

			return true;
		}

		return false;
	}
	
	private static void displayContextMenu(MapElement m, double x, double y) {
		hideContextMenu();

		//Create a new one
		contextMenu = new ContextMenu();
		contextMenu.setX(x);
		contextMenu.setY(y);

		if(m.getType() == MapElement.STATION) {
			stationContextMenu(contextMenu, m);
		} else if(m.getType() == MapElement.TRAIN) {
			trainContextMenu(contextMenu, m);
		} else if(m.getType() == MapElement.TRACK) {
			trackContextMenu(contextMenu, m);
		} else if(m.getType() == MapElement.YARD) {
			yardContextMenu(contextMenu, m);
		}

		AnchorPane ap = (AnchorPane)root.lookup("#Content");
		
		//Calculates offsets for full screen
		double offsetX = 215;
		double offsetY = 50;
		if(scene.getX() == scene.getY()) {
			offsetX -= 5;
			offsetY -= 25;
		}
		//Remove hard numbers for map start
		contextMenu.show(ap, x + stage.getX() + offsetX, y + stage.getY() + offsetY);
	}

	private static void stationContextMenu(ContextMenu contextMenu, MapElement m) {
		MenuItem item1 = new MenuItem(MapElement.getName(m.getType()) + " : " + ((String)m.getData()));

		Menu menu1 = new Menu("Info...");

		MenuItem item2 = new MenuItem("Sub Item");
		item2.setOnAction(new EventHandler<ActionEvent>() {
		    public void handle(ActionEvent e) {
		        System.out.println("Information!");
		    }
		});

		menu1.getItems().add(item2);
		contextMenu.getItems().addAll(item1, menu1);
	}

	private static void trainContextMenu(ContextMenu contextMenu, MapElement m) {
		MenuItem item1 = new MenuItem(MapElement.getName(m.getType()) + " " + m.getParentID());

		MenuItem item2 = new MenuItem("Set Speed/Authority");
		item2.setOnAction(new EventHandler<ActionEvent>() {
		    public void handle(ActionEvent e) {
		    	try {
		    		setSpeedAuthorityDialog();
		    	} catch(Exception ex) { }
		    }
		});

		contextMenu.getItems().addAll(item1, item2);
	}

	private static void trackContextMenu(ContextMenu contextMenu, MapElement m) {
		MenuItem item1 = new MenuItem(MapElement.getName(m.getType()) + " " + m.getParentID());
		item1.setOnAction(new EventHandler<ActionEvent>() {
		    public void handle(ActionEvent e) {
		        System.out.println("Element Type Dude!");
		    }
		});

		Menu menu1 = new Menu("Info...");
		
		//HACK
		TrainModel tm = trackModel.getLine("Green").getBlock(m.getParentID()).getTrain();
		String id = "NULL";
		if(tm != null)
			id = String.format("Train %d", tm.getTrainID());
		
		MenuItem item2 = new MenuItem(id);
		item2.setOnAction(new EventHandler<ActionEvent>() {
		    public void handle(ActionEvent e) {
		        System.out.println("Information!");
		    }
		});

		menu1.getItems().add(item2);
		contextMenu.getItems().addAll(item1, menu1);
	}

	private static void yardContextMenu(ContextMenu contextMenu, MapElement m) {
		MenuItem item1 = new MenuItem("Route Train...");
			item1.setOnAction(new EventHandler<ActionEvent>() {
			    public void handle(ActionEvent e) {
			    	try {
			    		routeTrainDialog();
			    	} catch(Exception ex) { }
			    }
			});
			contextMenu.getItems().addAll(item1);
	}
	
	private static void hideContextMenu() { 
		if(contextMenu != null) {
			contextMenu.hide();
		}
	}
	
	private static void calibrateScaling() {
		Line line = trackModel.getLine("Green");
		double sWidth = Double.POSITIVE_INFINITY;
		double sHeight = Double.POSITIVE_INFINITY;
		
		Block b;
		for(int i = 1; i < line.getNumBlock(); i++) {
			b = line.getBlock(i);
			
			if(sWidth > (graphicsEngine.CANVAS_WIDTH/b.getLocation()[0]))
				sWidth = (graphicsEngine.CANVAS_WIDTH/b.getLocation()[0]);
			
			if(sHeight > (graphicsEngine.CANVAS_HEIGHT/b.getLocation()[1]))
				sHeight = (graphicsEngine.CANVAS_HEIGHT/b.getLocation()[1]);

		}
		
		line = trackModel.getLine("Red");
		for(int i = 1; i < line.getNumBlock(); i++) {
			b = line.getBlock(i);
			
			if(sWidth > (graphicsEngine.CANVAS_WIDTH/b.getLocation()[0]))
				sWidth = (graphicsEngine.CANVAS_WIDTH/b.getLocation()[0]);
			
			if(sHeight > (graphicsEngine.CANVAS_HEIGHT/b.getLocation()[1]))
				sHeight = (graphicsEngine.CANVAS_HEIGHT/b.getLocation()[1]);

		}

		graphicsEngine.setScaling(sHeight, sWidth);		
	}


	/**
	 * This method is primarily for testing purposes only in order to verify the canvas instantiation.
	 * 
	 * @param gc Represents the graphics context which is used for drawing on the canvas element
	 */
	private static void drawTrack() {		
		//HACK
		//Line line = trackModel.getLine("Green");
		mapElements.add(new MapElement(mapElements.size(), MapElement.YARD , graphicsEngine.SCALE_WIDTH*trackModel.yard.getCoordinates()[0] + graphicsEngine.SCALE_WIDTH_OFFSET, graphicsEngine.SCALE_HEIGHT*trackModel.yard.getCoordinates()[1] + graphicsEngine.SCALE_HEIGHT_OFFSET));
		for(Line line : trackModel.theLines) {
			Router.addRoute(line.getName());
			
			Block b;
			for(int i = 1; i <= line.getNumBlock(); i++) {
				b = line.getBlock(i);
				trackElements.add(new MapElement(b.getBlockID(), MapElement.TRACK, new double[]{graphicsEngine.SCALE_WIDTH*b.getLocation()[0] + graphicsEngine.SCALE_WIDTH_OFFSET, graphicsEngine.SCALE_WIDTH*b.getLocation()[2] + graphicsEngine.SCALE_WIDTH_OFFSET}, new double[] {graphicsEngine.SCALE_HEIGHT*b.getLocation()[1] + graphicsEngine.SCALE_HEIGHT_OFFSET, graphicsEngine.SCALE_HEIGHT*b.getLocation()[3] + graphicsEngine.SCALE_HEIGHT_OFFSET}));
				trackElements.get(trackElements.size() - 1).setData(line.getName());
				
				if(b instanceof StationBlock) {
					//Adds the item to the router
					Router.routes.get(line.getName()).add(((StationBlock)b));
					mapElements.add(new MapElement(mapElements.size(), MapElement.STATION, graphicsEngine.SCALE_WIDTH*b.getLocation()[0] + graphicsEngine.SCALE_WIDTH_OFFSET, graphicsEngine.SCALE_HEIGHT*b.getLocation()[1] + graphicsEngine.SCALE_HEIGHT_OFFSET));
					mapElements.get(mapElements.size() - 1).setData(((StationBlock)b).getStationName());
				} else if(b instanceof ControllerBlock) {
					trackControllerElements.add(new MapElement(mapElements.size(), MapElement.TRACKCONTROLLER, graphicsEngine.SCALE_WIDTH*b.getLocation()[0] + graphicsEngine.SCALE_WIDTH_OFFSET, graphicsEngine.SCALE_HEIGHT*b.getLocation()[1] + graphicsEngine.SCALE_HEIGHT_OFFSET));
				}
			}
		}
		//END HACK
    }
	
	/**
	 * Applies full-screen mode to the program, which can be exited using the ESC key.
	 */
	public static void setFullScreen() {
		stage.setFullScreen(true);
		Main.log.write("Application: Full-screen entered.");
	}

	public static void registerTrainModel(TrainModel t) {
		trainModelList.add(t);		
	}

	public static void registerTrainController(TrainController tc) {
		trainControlList.add(tc);
	}

	public static void registerTrackController(TrackController tc) {
		trackControlList.add(tc);
	}
	
	/**
	 * Creates the visual representation of a train in the system which include but is not limited to:
	 * <p>
	 * <ul>
	 * 	<li>Side menu overview of each train module</li>
	 * 	<li>Visual elements on the map for a train</li>
	 * 	<li>Calls a creation method for a new train object</li>
	 * </ul>
	 * 
	 * @see Train
	 */
	@SuppressWarnings("unchecked")
	public static void addTrain() {
		//Logs the start of the process
		Main.log.write("Application: User initiated train creation process.");

		//Close the Route UI
		cstage.close();

		//Call to the CTC to add the train to the system
		office.addTrain((String)((ComboBox<String>)child.lookup("#RouteTLC")).getValue(), (String)((ComboBox<String>)child.lookup("#RouteTD")).getValue(), (String)((TextField)child.lookup("#RouteAT")).getText());
		
		//Finishes non-graphical train creation
		Main.log.write("Application: Train data created successfully in yard.");

		//Retrieve the side menu
		VBox vbtrainManager = (VBox)scene.lookup("#trainManager");
		
		//Prepares the AnchorPane for the TitledPane
		AnchorPane apContent = new AnchorPane();
		apContent.setMinSize(194, 190);
		apContent.setMaxSize(194, 190);
		
		generateTrainStatistics(apContent);
		
		//Appends the train to the left collection
		TitledPane tpTrain = new TitledPane("Train " + (office.getTrainCount() - 1), apContent);
		tpTrain.setId("train" + (office.getTrainCount() - 1));
		tpTrain.setUserData((office.getTrainCount() - 1));
		tpTrain.setOnMouseClicked(new EventHandler<MouseEvent>() {
        	@Override
        	public void handle(MouseEvent evt) {
        		if(evt.getButton().equals(MouseButton.PRIMARY)) {
        			int tid = (int)((TitledPane)evt.getSource()).getUserData();
        			
        			for(MapElement m : trainElements)
        				if(m.getParentID() == tid) {
        					selected = m;
        					mapHighlightElements.clear();
        					mapHighlightElements.add(new MapElement(m));
        					mapHighlightElements.get(mapHighlightElements.size() - 1).highlight();
        					break;
        				}
        					
        		}
        	}
        });
		
		vbtrainManager.getChildren().add(tpTrain);

		//DEPRECATED
		trainElements.add(new MapElement(trainElements.size(), MapElement.TRAIN, mapElements.get(0).getCenterX(), mapElements.get(0).getY().get(0)));
		//END

		//Draws the visual on the graph
		//trainModelList.get(office.getTrainCount()).mapElement = new MapElement(trainModelList.size(), MapElement.TRAIN, mapElements.get(0).getCenterX(), mapElements.get(0).getY());
		
		try {
			Platform.runLater(new Runnable() {
		        @Override
		        public void run() {
		        	graphicsEngine.render(trainElements);
		        }
			});
		} catch(Exception ex) {
			System.out.println(ex.getStackTrace());
		}

		//Confirms end of train creation
		Main.log.write("Application: Train is graphically rendered.");
	}
	
	/**
	 * Generates the labels which are updated to reflect the various attributes inside of a train object.
	 * This is added to the side menu train module.
	 * 
	 * @return Labels representing the trains internal state
	 * @see #addTrain
	 */
	public static void generateTrainStatistics(AnchorPane apContent) {
		Label labels[][] = new Label[12][3];
		
		//Generates the title labels for the train statistics
		labels[0][0] = new Label("GPSx");
		labels[1][0] = new Label("GPSy");
		labels[2][0] = new Label("Speed");
		labels[3][0] = new Label("Power");
		labels[4][0] = new Label("Acceleration");
		labels[5][0] = new Label("Brake Rate");
		labels[6][0] = new Label("EBrake");
		labels[7][0] = new Label("Doors");
		labels[8][0] = new Label("Lights");
		labels[9][0] = new Label("Passenger Count");
		labels[10][0] = new Label("Destination");
		labels[11][0] = new Label("Running Time");

		int localY = -10;
		for(int i = 0; i < labels.length; i++) {
			labels[i][0].setLayoutY(localY+=15);
			labels[i][0].setLayoutX(10);

			//Creates the semicolons for the data
			labels[i][1] = new Label(":");
			labels[i][1].setLayoutY(localY);
			labels[i][1].setLayoutX(100);

			//Creates the data labels
			labels[i][2] = new Label("NULL");
			labels[i][2].setLayoutY(localY);
			labels[i][2].setLayoutX(130);
		}
		
		for(Label l1[] : labels) {
			apContent.getChildren().add(l1[0]);
			apContent.getChildren().add(l1[1]);
			apContent.getChildren().add(l1[2]);
		}
		
		Label l[] = new Label[12];
		for(int i = 0; i < l.length; i++)
			l[i] = labels[i][2];
		
		trainPanes.put((office.getTrainCount() - 1), l);
	}
	
	public static void updateTrainPosition(int tid, int bid) {
		DBEntry tEntry = office.trainLog.getEntriesByHeader("ID", tid).get(0);
		
		for(int i = 0; i < trainElements.size(); i++) {
			if(trainElements.get(i).getParentID() == tid) {
				for(MapElement n: trackElements) {
					if(n.getParentID() == bid && ((String)n.getData()) == tEntry.getData(office.trainLog.getHeaderID("LINE"))) {
						trainElements.get(i).updatePosition(n.getX().get(0), n.getY().get(0));
						break;
					}
				}
				break;
			}
		}
	}
	
	/* Takes the currently selected train and deletes it from the system */
	public static void deleteTrain() {
		VBox trainManager = (VBox)scene.lookup("#trainManager");
		
		int count = 0;
		for(Node n : trainManager.getChildren()) {
		    if(n.isFocused()) {
		    	break;
		    }
		    count++;
		}		
		trainManager.getChildren().remove(count);

		//Logs the removal of a train from the system
		Main.log.write("Application: Train ID" + count + " has left the system.");
	}

	public static void toggleMBO() {
		office.bMBOEnabled = !office.bMBOEnabled;
		
		Circle MBOStatus = (Circle)root.lookup("#MBOStatus");
		if(office.bMBOEnabled) {
			MBOStatus.setFill(Color.rgb(51, 255, 133));
			Main.log.write("Application: MBO has been enabled.");
		} else {
			MBOStatus.setFill(Color.rgb(255, 51, 51));
			Main.log.write("Application: MBO has been disabled.");
		}
	}
		
	/* Creates a timed event */
	public static void timedEvent() {
		//Logs the beginning of a tick
		Main.log.write("Application: System tick initiated.");
		try {
			task = new Task<Void>() {
			    @Override public Void call() {
			    	//The task will recur until it is cancelled
			        while(true) {
			            if (isCancelled()) {
			               break;
			            }

						long millisLongStart = System.currentTimeMillis();

						systemTick();
						
						try {
							Platform.runLater(new Runnable() {
						        @Override
						        public void run() {
						        	updateTiles();
						        	updateTime();
						        }
							});
						} catch(Exception ex) {
							System.out.println(ex.getStackTrace());
						}
						
						long millisLongEnd = System.currentTimeMillis();
						long tickLen = (millisLongEnd - millisLongStart);
						//Increments the system timer
						sysTime += 1000;
						
						Main.log.write("Application: System tick completed in " + tickLen + "ms.");
						
			            //Block the thread for a short time, but be sure
			            //to check the InterruptedException for cancellation
			            try {
		            		long iSleepTime = (lTick/iSimulationMultiplier) - tickLen;
		            		if(iSleepTime >= 0) {
		            			Thread.sleep(iSleepTime);
		            		}
			            }
			            catch (InterruptedException interrupted) {
			                if (isCancelled()) {
			                    break;
			                }
			            }
			        }
			        return null;
			    }
			};
			
			new Thread(task).start();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}

	/* Creates a timed event for the UI*/
	public static void graphicsRefresh() {
		//Logs the beginning of a tick
		//Main.log.write("Application: GUI Refresh.");
		try {
			graphicsThread = new Task<Void>() {
			    @Override public Void call() {
			    	//The task will recur until it is cancelled
			        while(true) {
			            if (isCancelled()) {
			               break;
			            }

			            //Block the thread for a short time, but be sure
			            //to check the InterruptedException for cancellation
			            try {
			                Thread.sleep(250);
			            } catch (InterruptedException interrupted) {
			                if (isCancelled()) {
			                    break;
			                }
			            }

						try {
							Platform.runLater(new Runnable() {
						        @SuppressWarnings("unchecked")
								@Override
						        public void run() {
						        	graphicsEngine.clear();
									graphicsEngine.render(mapHighlightElements, trackElements, mapElements, trainElements, trackControllerElements);
						        }
							});
						} catch(Exception ex) {
							System.out.println(ex.getStackTrace());
						}
			        }
			        return null;
			    }
			};
			
			new Thread(graphicsThread).start();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void cancel() {
		try {
			graphicsEngine.clear();
			graphicsThread.cancel();
		} catch(Exception ex) {
			System.out.println(ex.getStackTrace());
		}
	}
	
	//Check ordering of ticks and simplification of ticking
	public static void systemTick() {		
		if(office.bMBOEnabled) {
			//mbo.tick();
		}
		
		for( TrackController tc : trackControlList) {
			tc.tick();
		}

		office.tick();
		
		trackModel.tick();
		
		for( TrainModel tm : trainModelList ) {
			tm.tick();
		}
		
		for( TrainController tc : trainControlList ) {
			tc.tick();
		}
	}

	public static void updateTiles() {
		Label[] trainStats;
	
		for(TrainModel t : trainModelList) {
			trainStats = trainPanes.get(t.getTrainID());
			//trainStats[0].setText(Double.toString(mbo.getTrainPosition()[0]));
			//trainStats[1].setText(Double.toString(mbo.getTrainPosition()[1]));
			trainStats[0].setText(Double.toString(t.getCurrentBlock().getLocation()[0]));
			trainStats[1].setText(Double.toString(t.getCurrentBlock().getLocation()[1]));
			trainStats[2].setText(String.format("%.3f", (2.23694*2.23694*t.getSpeed())));
			trainStats[3].setText(String.format("%.1f", t.getCurrentPower()));
			trainStats[4].setText(String.format("%.3f", t.getCurrentAccel()));
			trainStats[5].setText(String.format("%.3f", t.getCurrentBrake()));
			trainStats[6].setText(t.getCurrentEBrake());
			trainStats[7].setText(t.getDoorState());
			trainStats[8].setText(t.getLightState());
			trainStats[9].setText(Integer.toString(t.getNumPassengers()));
			trainStats[10].setText(((String)office.trainLog.getDataById(t.getTrainID(), "DESTINATION")));
			long time = t.getElapsedTime()/lTick;
			trainStats[11].setText(String.format("%02d", time/60) + ":" + String.format("%02d", time%60));
		}
	}
	
	public static void updateTime() { 
		Date d = new Date(sysTime);
		SimpleDateFormat f = new SimpleDateFormat("MMMMM d");
		Label l = ((Label)root.lookup("#timeDate"));
		l.setText(f.format(d));
		f = new SimpleDateFormat("kk:mm");
		l = ((Label)root.lookup("#timeNumber"));
		l.setText(f.format(d));
	}
	
	public static void setEngineFailure() {
		if(selected != null  && selected.getType() == MapElement.TRAIN) {
			//Find the train
			for(int i = 0; i < trainModelList.size(); i++) {
				if(trainModelList.get(i).getTrainID() == selected.getParentID()) {
					if(trainModelList.get(i).getEngineFailure() == "FAILURE")
						trainModelList.get(i).repairEngineFailure();
					else
						trainModelList.get(i).initiateEngineFailure();
					break;
				}
			}
		} else {
	    	try {
				messageDialog("You have not selected any trains!");
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	    	return;
	    }
	}
	
	public static void setBrakeFailure() {
		if(selected != null  && selected.getType() == MapElement.TRAIN) {
			//Find the train
			for(int i = 0; i < trainModelList.size(); i++) {
				if(trainModelList.get(i).getTrainID() == selected.getParentID()) {
					if(trainModelList.get(i).getBrakeFailure() == "FAILURE")
						trainModelList.get(i).repairBrakeFailure();
					else
						trainModelList.get(i).initiateBrakeFailure();
					break;
				}
			}
		} else {
	    	try {
				messageDialog("You have not selected any trains!");
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	    	return;
	    }
	}
	
	public static void setTrackFailure() {
		if(selected != null  && selected.getType() == MapElement.TRACK) {
			Block b = trackModel.getLine("Green").getBlock(selected.getParentID());
			//for(Line l : trackModel.getLines()) {
				//HACK CHECK FOR LINE STUFF
				if(b.hasBrokenRail)
					b.setBrokenRail(false);
				else
					b.setBrokenRail(true);
			//}
		} else {
	    	try {
				messageDialog("You have not selected any track!");
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	    	return;
	    }
	}
	
	public static void setTrackCircuitFailure() {
		if(selected != null  && selected.getType() == MapElement.TRACK) {
			Block b = trackModel.getLine("Green").getBlock(selected.getParentID());
			//for(Line l : trackModel.getLines()) {
				//HACK CHECK FOR LINE STUFF
				if(b.hasCircuitFailure)
					b.setCircuitFailure(false);
				else
					b.setCircuitFailure(true);
			//}
		} else {
	    	try {
				messageDialog("You have not selected any track!");
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	    	return;
	    }
	}

	public static void setSpeedAuthority() {
		//Close the Route UI
		cstage.close();

		//Send the data to Logan
		int tid = Integer.parseInt(((TextField)child.lookup("#SetTID")).getText());
		office.setSpeed(tid, 0.4474*Double.parseDouble(((TextField)child.lookup("#SetTS")).getText()));
		office.setAuthority(tid, Integer.parseInt(((TextField)child.lookup("#SetTA")).getText()));
	}

	public static void setSpeedAuthorityDialog() throws Exception {
		Main.log.write("Application: Set Speed and Authority dialog opened.");
		//Loads the routing window
		child = FXMLLoader.load(Main.class.getResource("Set UI.fxml"));
	    cstage = new Stage();
	    cstage.setTitle("Set Speed and Authority");
	    cstage.initStyle(StageStyle.UTILITY);
	    Scene cscene = new Scene(child, 410, 260);
	    cstage.setScene(cscene);

	    //Set the train id
	    TextField trainID = ((TextField)child.lookup("#SetTID"));
	    
	    if(selected != null  && selected.getType() == MapElement.TRAIN) {
	    	trainID.setText(Integer.toString(selected.getParentID()));
	    
	    	//Gets the train model
		    /*TrainModel t;
		    for(TrainModel tm : trainModelList) {
		    	if( == selected.getParentID()) {
		    		t = tm;
		    		break;
		    	}
		    }*/
		    
	    	//((TextField)child.lookup("#SetTS")).setPromptText(t.getSpeed());
	    } else {
	    	messageDialog("You have not selected any trains!");
	    	return;
	    }
	    trainID.setDisable(true);
	    
	    // Show the dialog and wait until the user closes it
	    cstage.showAndWait();
	}

	public static void routeTrainDialog() throws Exception {
		Main.log.write("Application: Routing dialog opened.");
		//Loads the routing window
		child = FXMLLoader.load(Main.class.getResource("Route UI.fxml"));
	    cstage = new Stage();
	    cstage.setTitle("Route a Train");
	    cstage.initStyle(StageStyle.UTILITY);
	    Scene cscene = new Scene(child, 410, 310);
	    cstage.setScene(cscene);
	    
	    //Generates the missing control
	    ComboBox<String> cmb = new ComboBox<String>();
	    cmb.getItems().addAll(
	        "Green","Red");
	    cmb.setId("RouteTLC");
	    cmb.setLayoutX(163);
	    cmb.setLayoutY(160);
	    //Requeries the possible destinations based on the line change
	    cmb.setOnAction(new EventHandler<ActionEvent>() {
	    	@SuppressWarnings({ "unchecked", "rawtypes" })
			@Override
	    	public void handle(ActionEvent evt) {
	    		ComboBox<String> destinations = ((ComboBox<String>)child.lookup("#RouteTD"));

	    		//Passes either a line name or macro
	    		ArrayList<String> items = new ArrayList<String>();
 	    		for(StationBlock sb : trackModel.getLine(((String)((ComboBox)child.lookup("#RouteTLC")).getValue())).getStations()) {
	    			items.add(sb.getStationName());
	    		}
	    		
	    		destinations.getItems().setAll(items);
	    	}
	    });
	    //cmb.setValue("Green");
	    ((AnchorPane)child.lookup("#RoutePane")).getChildren().add(cmb);
	    
	    //Set the train id
	    TextField trainID = ((TextField)child.lookup("#RouteTID"));
	    trainID.setText(Integer.toString(office.getTrainCount()));
	    trainID.setDisable(true);
	    
	    // Show the dialog and wait until the user closes it
	    cstage.showAndWait();
	}
	
	public static void aboutSoftwareDialog() throws Exception {
		Main.log.write("Application: About Software dialog opened.");
		//Loads the about window
		child = FXMLLoader.load(Main.class.getResource("About UI.fxml"));
	    cstage = new Stage();
	    cstage.setTitle("About OCSD");
	    cstage.initStyle(StageStyle.UTILITY);
	    Scene cscene = new Scene(child, 410, 160);
	    cstage.setScene(cscene);

	    // Show the dialog and wait until the user closes it
	    cstage.showAndWait();
	}

	public static void messageDialog(String message) throws Exception {
		Main.log.write("Application: About Software dialog opened.");
		//Loads the about window
		child = FXMLLoader.load(Main.class.getResource("Message UI.fxml"));
	    cstage = new Stage();
	    cstage.setTitle("System Message");
	    cstage.initStyle(StageStyle.UTILITY);
	    Scene cscene = new Scene(child, 260, 85);
	    cstage.setScene(cscene);

	    Label messageLabel = ((Label)child.lookup("#message"));
	    messageLabel.setText(message);

	    // Show the dialog and wait until the user closes it
	    cstage.showAndWait();
	}
	
	public static void updateMultiplier() {
		Slider s = ((Slider)root.lookup("#systemMultiplier"));
		double mult = s.getValue();
		
		if(mult == 0) {
			iSimulationMultiplier = 1;
		} else {
			iSimulationMultiplier = (int)mult;
		}
	}
	
	public static void loadLogFile()
	{
	    File logFile = new File(log.logFileName);
	    
	    try
	    {
	    	java.awt.Desktop.getDesktop().open(logFile);
	    }
	    catch( Exception e )
	    {
	    	System.out.println(e.getStackTrace());
	    }
	}
}
